from IPython.display import HTML
HTML('''<script>
code_show=true;
function code_toggle() {
if (code_show){
$('div.input').hide();
} else {
$('div.input').show();
}
code_show = !code_show
}
$( document ).ready(code_toggle);
</script>
<form action="javascript:code_toggle()"><input type="submit" value="Click here to toggle on/off the raw code."></form>''')
import pandas as pd
import numpy as np
from numpy import inf
import plotly.offline as py
import plotly.graph_objs as go
import plotly.tools as tools
# Set notebook mode to work in offline
py.init_notebook_mode()
measured_fuel_consumption = [15.06, 16.09, 15.54, 15.29, 15.09, 14.11, 14.95, 14.89, 15.55, 15.74, 16.98, 15.57]
torque_engine_max = 500; # Nm
gears = [4.377, 2.859, 1.921, 1.368, 1, 0.82, 0.728]
#gears_overall = [10.81, 7.06, 4.74, 3.38, 2.47, 2.03, 1.8]
final_ratio = 2.47
reverse = 3.416
wheel = {'width_mm':225, 'profile_perc':45, 'diameterRim_inches':17}
wheel['diameterRim_mm'] = wheel['diameterRim_inches']*25.4
wheel['diameterTot_m'] = (wheel['diameterRim_mm'] + 2*(wheel['profile_perc']/100*wheel['width_mm']) )/1000
print("Max engine torque", torque_engine_max, " Nm")
print("Wheel data", wheel)
print("Gear ratio: ", gears)
print("Final ratio: ", final_ratio)
print("Fuel consumption estimated from tank refills:", measured_fuel_consumption)
df = pd.read_csv('logs/2019-04-12h06_29_02.761616_obdData.log',sep=';')
df['Time_s'] = df['Time_s']-df['Time_s'][0]
df['ENGINE_LOAD_Nm']=df['ENGINE_LOAD'].apply(lambda x: x*torque_engine_max/100)
df['ENGINE_POWER_W']=df['ENGINE_LOAD_Nm']*(df['RPM'].apply(lambda x: x*2*np.pi/60))
df['ENGINE_POWER_hp']=df['ENGINE_POWER_W'].apply(lambda x: x*0.00134102209)
df['SPEED'] = df['SPEED'] + 0 #+3 correct for actual speed (TO BE CHEDKED WITH GPS=
#df
Engine load data is clipped to 100%, and not really very usefull to assess behavior at high torques. For now let's assume that the engine torque is the engine load data % multiplied the maximum torque the engine can deliver.
TODO: BSFC once the instantaneous fuel flow is estimated. SEE AT THE BOTTOM, section BSFC.
fig = tools.make_subplots(rows=1, cols=2)
pRpmPower = go.Scatter(
x=df['RPM'],#df['Time_s'],
y=df['ENGINE_LOAD_Nm'],
mode='markers'
)
fig.append_trace(pRpmPower, 1, 1)
pRpmTorque = go.Scatter(
x=df['RPM'],#df['Time_s'],
y=df['ENGINE_POWER_hp'],
mode='markers'
)
fig.append_trace(pRpmTorque, 1, 2)
fig['layout']['xaxis1'].update(title='Engine (RPM)')
fig['layout']['xaxis2'].update(title='Engine load (Nm)')
fig['layout']['yaxis1'].update(title='Engine (RPM)')
fig['layout']['yaxis2'].update(title='Engine power (HP)')
fig['layout'].update(height=500, width=1000)
py.iplot(fig)
Note that the estimated engine power for some outliers is above the nominal engine power of 200 horse power.
pGear = [go.Scatter(
x=df['RPM'],
y=df['SPEED'],
mode='markers',
name = 'Data')]
rpm = np.linspace(0, max(df['RPM'])+100, num=2)
gears_overall = []
for i in range(0,len(gears)):
wheel_rpm = rpm/(gears[i]*final_ratio)
wheel_radps = wheel_rpm*2*np.pi/60
kmph = wheel_radps*(wheel['diameterTot_m']/2)*3.6
gears_overall.append(kmph[1]/rpm[1])
pGear.append(
go.Scatter(
x=rpm,
y=kmph,
name = str(i+1)+' gear'
)
)
RPM_idle = 650
dist_thrs = 5 # km/h
indxs = [[], [], [], [], [], [], []]
for i in range(0, len(df['SPEED'])):
x = df['RPM'][i]
y = df['SPEED'][i]
distances = []
indx_min = 0
dist_min = 1e6
if x <= RPM_idle:
indx_min = 0
else:
for j in range(0, len(gears_overall)):
d = (abs(gears_overall[j]*x - 1*y )/np.sqrt(gears_overall[j]**2+1))
if d < dist_min:
indx_min = j
dist_min = d
if dist_min < dist_thrs:
indxs[indx_min].append(i)
df_1 = df.loc[indxs[0], :]
df_2 = df.loc[indxs[1], :]
df_3 = df.loc[indxs[2], :]
df_4 = df.loc[indxs[3], :]
df_5 = df.loc[indxs[4], :]
df_6 = df.loc[indxs[5], :]
df_7 = df.loc[indxs[6], :]
columns = ['Time in gear (%)', 'Average torque in gear (%)']
df_gearStatistics = pd.DataFrame(columns=columns)
print('Statistics')
for i in range(0, len(indxs)):
percTimeInGear = np.round(len(df.loc[indxs[i], :])/len(df)*100)
avrgTorqueInGear = np.round( np.mean(df['ENGINE_LOAD'][indxs[i]]))
#print('\tgear ', i+1, ': percTimeInGear', percTimeInGear, '%, avg.Trq ', round(avrgTorqueInGear), '%')
df_gearStatistics.loc[i] = [percTimeInGear, avrgTorqueInGear]
Plot on the left: logged engine speed and vehicle speed it is possible to assess the gear ratios of the 7-gear automatic transmission. The match between measured ratios and nominal ones from datasheet is good, meaning that the error of the measured speed is small.
Plot on the right: by plotting the vehicle speed vs the engine load and colouring the points based on estimated gear it should be possible to reverse engineer the gear shifting strategy. However, the data is very noisy, mainly due to the fact that this automatic transmission has a torque converter, hence a complex behavior.
colors = ['rgba(200, 0, 0, 0.2)',
'rgba(150, 50, 0, 0.2)',
'rgba(100, 100, 0, 0.2)',
'rgba(50, 150, 100, 0.2)',
'rgba(0, 200, 0, 0.2)',
'rgba(0, 150, 50, 0.2)',
'rgba(0, 100, 100, 0.2)',]
colors = ['rgba(200, 0, 0, 0.2)',
'rgba(0, 200, 0, 0.2)',
'rgba(0, 0, 200, 0.2)',
'rgba(150, 50, 0, 0.2)',
'rgba(0, 50, 100, 0.2)',
'rgba(100, 0, 100, 0.2)',
'rgba(75, 75, 75, 0.2)',]
pGearColors = []
pEngineColors = []
fig = tools.make_subplots(rows=1, cols=2)
for i in range(0,len(gears)):
pGearColors = (
go.Scatter(
x=rpm,
y=gears_overall[i]*rpm,
name = str(i+1)+' gear',
mode='lines',
line = dict(color = colors[i]),
showlegend=False
)
)
fig.append_trace(pGearColors, 1, 1)
pGearColors = (
go.Scatter(
x= df['RPM'][indxs[i]],
y= df['SPEED'][indxs[i]],
#name = str(i+1)+' gear',
mode='markers',
marker = dict(color = colors[i]),
showlegend=False
)
)
fig.append_trace(pGearColors, 1, 1)
pEngineColors = (
go.Scatter(
x= df['SPEED'][indxs[i]],
y=df['ENGINE_LOAD_Nm'],
#name = str(i+1)+' gear',
mode='markers',
marker = dict(color = colors[i]),
showlegend=False
)
)
fig.append_trace(pEngineColors, 1, 2)
#fig.append_trace(pGearColors, 1, 1)
#fig.append_trace(pEngineColors, 1, 2)
fig['layout']['xaxis1'].update(title='Engine (RPM)')
fig['layout']['xaxis2'].update(title='Vehicle speed (km/h)')
fig['layout']['yaxis1'].update(title='Vehicle speed (km/h)')
fig['layout']['yaxis2'].update(title='Engine load (Nm)')
fig['layout'].update(height=500, width=1000)
py.iplot(fig)
# Classifier to reverse engineer the gear shifting strategy
df_gearStatistics
trace0 = go.Scatter(
x=df_gearStatistics.index,
y=df_gearStatistics['Time in gear (%)'],
mode='markers',
marker=dict(
color=df_gearStatistics['Average torque in gear (%)'],
size=df_gearStatistics['Average torque in gear (%)'],
showscale=True
)
)
data = [trace0]
layout = go.Layout(
title='Gear management statistics',
xaxis=dict( title='Gear (/)' ),
yaxis=dict( title='Time in gear (%)' )
)
py.iplot(go.Figure(data=data, layout=layout))
Fuel consumption is estimated by assuming: A2D = 14.6 air-to-diesel stechiometric ratio DD = density of diesel = 0.832 # kg/litre
Measured values:
MAF: (grams/sec) Mass Air Flow
S : (km/h) vehicle longitudinal speed
The fuel mass flow MFF (grams/sec) is:
MFF = MAF/A2D
Fuel volumetric flow (litre/h):
VFF = MFF/(DD*1000)*3600
Fuel consumption KMPL (km/litre):
KMPL = S/VFF
TODO: fuel consumption seems to be over estimated. Correction factor needed, because fuel tank refill statistics state that the average fuel consumption is 15 km/l.
df['MAF'] = df['MAF']
pMAF = go.Scatter(
x=df['Time_s'],
y=df['MAF'],
name = 'MAF'
#mode='markers+text'
)
pSPEED = go.Scatter(
x=df['Time_s'],
y=df['SPEED'],
name = 'SPEED'
#mode='markers+text'
)
layout = go.Layout(
#title='Powertrain performance',
xaxis=dict( title='Time (s)' ),
yaxis=dict( title='' )
)
#fig = go.Figure(data=pGear, layout=layout)
#py.iplot([pSPEED, pMAF])
layout = go.Layout(
title='Powertrain performance',
xaxis=dict( title='Time (s)' ),
yaxis=dict( title='' )
)
py.iplot({"data":[pSPEED, pMAF], "layout":layout})
# Natural gas: 17.2
# Gasoline: 14.7
# Propane: 15.5
# Ethanol: 9
# Methanol: 6.4
# Hydrogen: 34
# Diesel: 14.6
AIR2DIESEL_RATIO = 14.6
DENSITYDIESEL = 0.832 # kg/litre !!! ADJUST THIS VALUE IF NEEDED
df['MFF'] = df['MAF']/AIR2DIESEL_RATIO # (grams/sec) Mass Fuel Flow
df['VFF'] = df['MFF']/(DENSITYDIESEL*1000)*3600 # (litres/h) Volume Fuel Flow
df['KMPL'] = df['SPEED']/df['VFF'] # (km/litre) Fuel consumption
pMFF = go.Scatter(
x=df['Time_s'],
y=df['MFF'],
name = 'MFF'
#mode='markers+text'
)
pKMPL = go.Scatter(
x=df['Time_s'],
y=df['KMPL'],
name = 'KMPL'
#mode='markers+text'
)
pVFF = go.Scatter(
x=df['Time_s'],
y=df['VFF'],
name = 'VFF'
#mode='markers+text'
)
layout = go.Layout(
title='Economy',
xaxis=dict( title='Time (s)' ),
yaxis=dict( title='Fuel consumption (km/l)' )
)
py.iplot({"data":[pKMPL], "layout":layout})
It is possible to compare the estimated fuel consumption against the fuel consumption measured by filling the tank at every refuel.
print("Estimated average fuel consumption while moving: ", np.mean(df['KMPL']), " km/l")
print("Measured average fuel consumption: ", np.mean(measured_fuel_consumption), " km/l")
The estimated fuel consumption is 50% off the measured value, hance a factor of approx. 2 is needed to correct the estimation.
Assuming a correction factor of 2 for the estimated fuel consumption KMPL such that its average value is closer to the measured fuel consumption:
KMPL_corr = 2*KMPL
and that the engine load in Nm is it is possible to try to reconstruct the engine BSFC map.
correction_factor = 2
df['BSFC'] = correction_factor*df['MFF']/df['ENGINE_POWER_W'];
df['BSFC'] = df['BSFC'].apply(lambda x: x*3.6*1e6)
df_red = df[(df['ENGINE_LOAD_Nm']>50) & (df['RPM']>500)]
trace0 = go.Scatter(
x=df_red['RPM'],
y=df_red['ENGINE_LOAD_Nm'],
mode='markers',
marker=dict(
color=df_red['BSFC'],
size=10,
showscale=True,
opacity = 0.5,
)
)
data = [trace0]
layout = go.Layout(
title='BSFC g/(kWâ‹…h)',
xaxis=dict( title='Engine speed (RPM)' ),
yaxis=dict( title='Engine load (Nm)' ),
width=900,
height=700
)
py.iplot(go.Figure(data=data, layout=layout))